﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HenIT;
using System.Diagnostics;

namespace EventScavenger
{
    class Program
    {
        private static bool enableTraceToConsole = false;
        static void Main(string[] args)
        {
            if (args.Length < 2 || CommandLineUtils.IsCommand(args, "-?", "/?", "-h", "/h"))
            {
                ShowHelp();
            }
            else
            {
                string lastStep = "Reading command line parameters";
                string machineName = args[0];
                string eventLogName = args[1];
                string esDBServer = Properties.Settings.Default.SQLServer;
                string esDatabase = Properties.Settings.Default.Database;
                bool trustServerCertificate = CommandLineUtils.IsCommand(args, "-trustServerCertificate", "/trustServerCertificate", "-trust", "/trust");
                DateTime fromDate = DateTime.Parse(CommandLineUtils.GetCommand(args, (new DateTime(1900, 1, 1)).ToString("yyyy-MM-dd"), "-fd:", "/fd:"));
                DateTime toDate = DateTime.Parse(CommandLineUtils.GetCommand(args, (DateTime.Now).ToString("yyyy-MM-dd"), "-td:", "/td:"));
                esDBServer = CommandLineUtils.GetCommand(args, esDBServer, "-s:", "/s:");
                esDatabase = CommandLineUtils.GetCommand(args, esDatabase, "-d:", "/d:");
                enableTraceToConsole = CommandLineUtils.IsCommand(args, "-traceOn", "/traceOn");

                Stopwatch sw = new Stopwatch();

                try
                {
                    EventScavengerDAL dal = new EventScavengerDAL();
                    dal.Server = esDBServer;
                    dal.Database = esDatabase;
                    dal.RaiseErrorMessage += dal_RaiseErrorMessage;
                    dal.TrustServerCertificate = trustServerCertificate;
                    Console.WriteLine("Setting connection");
                    dal.SetConnection();

                    Console.WriteLine("Checking machine/log entry");
                    MachineLogEntry mle = dal.GetMachineLogEntry(machineName, eventLogName);
                    if (mle == null || mle.MachineLogID == -1)
                    {
                        Console.WriteLine("Computer name/Log name does not exist in database! First create an entry in the EventScavenger database.");
                    }
                    else
                    {
                        List<EventLogEntryEx> eventBuffer = new List<EventLogEntryEx>();
                        List<EventLogEntryEx> batch = null;
                        int batchSize = Properties.Settings.Default.BatchSize;

                        Console.WriteLine("Opening event log on {0}\\{1}", machineName, eventLogName);
                        using (EventLog log2Read = new EventLog(eventLogName, machineName))
                        {
                            Console.WriteLine("  Found {0} entries\r\nReading...", log2Read.Entries.Count);
                            int index = log2Read.Entries.Count - 1;
                            for (int i = index; i >= 0; i--)
                            {
                                eventBuffer.Add(EventLogEntryEx.FromEventLogEntry(log2Read.Entries[i]));
                                if (eventBuffer.Count % batchSize == 0)
                                {
                                    Console.Write(".");
                                }
                            }

                            #region Batch inserts
                            Console.WriteLine("\r\nStarting to insert batches...");
                            int batchNo = 1;
                            while (eventBuffer.Count > batchSize)
                            {
                                //Console.Write("Inserting batch {0}", batchNo);
                                lastStep = "Getting new batch of event log entries";
                                sw.Restart();
                                batch = eventBuffer.Take(batchSize).ToList();
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Getting new batch of event log entries", sw.Elapsed.TotalSeconds, mle.ToString());

                                lastStep = "Inserting batch of event log entries";
                                sw.Restart();
                                XmlBatchInsert(dal, mle, batch);
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Inserting batch of event log entries", sw.Elapsed.TotalSeconds, mle.ToString());

                                lastStep = "Getting newest entry time from batch";
                                sw.Restart();
                                mle.NewestEntry = batch.Max(en => en.TimeGenerated);
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Getting newest entry time from batch", sw.Elapsed.TotalSeconds, mle.ToString());

                                lastStep = "Removing batch of event log entries";
                                sw.Restart();
                                eventBuffer.RemoveRange(0, batchSize);
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Removing batch of event log entries", sw.Elapsed.TotalSeconds, mle.ToString());

                                lastStep = "Writing last polling time";
                                sw.Restart();
                                dal.SetLastMachineLogPollingTime(mle, mle.NewestEntry);
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Writing last polling time", sw.Elapsed.TotalSeconds, mle.ToString());

                                //Console.WriteLine(" done");
                                Console.Write(".");
                                batchNo++;
                            }
                            if (eventBuffer.Count > 0)
                            {
                                Console.WriteLine(".");
                                //Console.Write("Inserting last batch");
                                lastStep = "Inserting batch of event log entries";
                                sw.Restart();
                                XmlBatchInsert(dal, mle, eventBuffer);
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Inserting batch of event log entries", sw.Elapsed.TotalSeconds, mle.ToString());

                                lastStep = "Getting newest entry time from batch";
                                sw.Restart();
                                mle.NewestEntry = eventBuffer.Max(en => en.TimeGenerated);
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Getting newest entry time from batch", sw.Elapsed.TotalSeconds, mle.ToString());

                                lastStep = "Writing last polling time";
                                sw.Restart();
                                dal.SetLastMachineLogPollingTime(mle, mle.NewestEntry);
                                sw.Stop();
                                TraceDuration("InsertEntriesIntoDB", "Writing last polling time", sw.Elapsed.TotalSeconds, mle.ToString());
                                //Console.WriteLine(" done");
                            }
                            #endregion
                        }
                        Console.WriteLine("Import done");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Last Step {0}\r\n{1}", lastStep, ex.ToString());
                }
            }
        }

        static void dal_RaiseErrorMessage(string message)
        {
            Console.WriteLine("Data access error: {0}", message);
        }

        private static void XmlBatchInsert(EventScavengerDAL dal, MachineLogEntry mle, List<EventLogEntryEx> batch)
        {
            Stopwatch sw = new Stopwatch();
            Stopwatch sw2 = new Stopwatch();
            string message;
            try
            {
                System.IO.MemoryStream outstream = new System.IO.MemoryStream();
                using (System.Xml.XmlTextWriter wrt = new System.Xml.XmlTextWriter(outstream, Encoding.UTF8))
                {
                    //wrt.Formatting = System.Xml.Formatting.Indented;
                    //wrt.Indentation = 1;
                    //wrt.IndentChar = ' ';
                    wrt.WriteStartDocument();
                    wrt.WriteStartElement("rs");

                    foreach (EventLogEntryEx ele in batch)
                    {

                        wrt.WriteStartElement("r");
                        wrt.WriteAttributeString("cn", mle.Machine);
                        wrt.WriteAttributeString("l", mle.LogName);
                        wrt.WriteAttributeString("r", ele.Index.ToString());
                        wrt.WriteAttributeString("e", ele.InstanceId.ToString());
                        wrt.WriteAttributeString("t", ele.TimeGenerated.ToString("yyyy-MM-ddTHH:mm:ss"));
                        wrt.WriteAttributeString("s", ele.Source);
                        wrt.WriteAttributeString("et", ele.EntryType);
                        wrt.WriteAttributeString("c", N(ele.Category));
                        wrt.WriteAttributeString("u", N(ele.UserName));

                        sw2.Start();
                        message = ele.Message;
                        sw2.Stop();
                        sw.Start();
                        try
                        {
                            if (message.Length == 0)
                            {
                                string strings = Encoding.UTF8.GetString(ele.Data, 0, ele.Data.Length);
                                if (strings != null && strings.Length > 0)
                                    if (!strings.Contains('�'))
                                        message = strings;
                            }
                        }
                        catch (Exception dataEx)
                        {
                            Trace.WriteLine("XmlBatchInsert:Data stuff" + dataEx.Message);
                        }
                        if (message.IndexOf("\r\n") > -1)
                            message = message.Replace("\r\n", "\\r\\n");
                        sw.Stop();

                        wrt.WriteAttributeString("m", message);
                        wrt.WriteEndElement(); //r
                    }

                    wrt.WriteEndElement(); //rs
                    wrt.WriteEndDocument();
                    wrt.Flush();

                    outstream.Position = 0;
                    string infoResult = dal.InsertEventLogEntries(outstream);
                }
                TraceDuration("XmlBatchInsert", "Total duration part1", sw.Elapsed.TotalSeconds, "");
                TraceDuration("XmlBatchInsert", "Total duration part2", sw2.Elapsed.TotalSeconds, "");
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }
        }

        private static void ShowHelp()
        {
            Console.WriteLine("Usage: EventBulkImporter.exe <computerName> <eventLogName> [-s:EventScavenger server] [-d:EventScavenger database] [-fd:fromDate] [-td:toDate]");
        }

        #region TraceDuration
        private static void TraceDuration(string methodName, string stepName, double totalSeconds, string details)
        {
            Trace.WriteLine(string.Format("{0} sec - {1} - {2} - {3}", totalSeconds, methodName, stepName, details));
            if (enableTraceToConsole)
            {
                Console.WriteLine(string.Format("{0} sec - {1} - {2} - {3}", totalSeconds, methodName, stepName, details));
            }
        }
        #endregion

        #region Null handling
        private static string N(object anyVal)
        {
            if (anyVal == null)
                return "";
            else
                return anyVal.ToString();
        }
        #endregion
    }
}
